package org.efficientsoap.client;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;

import org.efficientsoap.client.EfficientSoapFault.Code;
import org.efficientsoap.client.EfficientSoapFault.Reason;
import org.efficientsoap.client.EfficientSoapFault.SubCode;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;



import android.util.Log;

public class EfficientSoapDeserializer<T> {

	/**
	 * Namespace for envelope 2003/05
	 * */
	public static final String ENVELOPE0503 = "http://www.w3.org/2003/05/soap-envelope";

	/**
	 * Namespace for encoding 2003/05
	 */
	public static final String ENCODING0503 = "http://www.w3.org/2003/05/soap-encoding";

	/**
	 * Namespace for envelope
	 * */
	public static final String ENVELOPE = "http://schemas.xmlsoap.org/soap/envelope/";

	/**
	 * Namespace for encoding
	 */
	public static final String ENCODING = "http://schemas.xmlsoap.org/soap/encoding/";

	/**
	 * Namespace for XML Schema
	 */
	public static final String XSD = "http://www.w3.org/2001/XMLSchema";

	/**
	 * Namespace for XMLSchema instance
	 */
	public static final String XSI = "http://www.w3.org/2001/XMLSchema-instance";

	/**
	 * Namespace for XML Schema 1999
	 */
	public static final String XSD1999 = "http://www.w3.org/1999/XMLSchema";

	/**
	 * Namespace for XMLSchema-instance 1999
	 */
	public static final String XSI1999 = "http://www.w3.org/1999/XMLSchema-instance";

	/**
	 * constant for SOAP Version 1.0
	 */
	public static final int VERSION10 = 10;

	/**
	 * constant for SOAP Version 1.1
	 */
	public static final int VERSION11 = 11;

	/**
	 * constant for SOAP Version 1.2
	 */
	public static final int VERSION12 = 12;

	private static final String LOGGING_TAG = EfficientSoapDeserializer.class
			.getSimpleName();
	
	protected XmlPullParser parser;
	
	private T responseObject;
	private IBodyDeserializer<T> bodyDeserializer;
	private IHeaderDeserializer headerDeserializer;
	private IFaultDeserializer faultDeserializer;
	
	

	private int soapVersion = VERSION10;
	private String soapNamespace = null;

	
	
	public T getResponseObject() {

		return this.responseObject;
	}

	public String getSoapNamespace() {
		return this.soapNamespace;
	}

	public void setBodyDeserializer(IBodyDeserializer<T> bodyDeserializer) {
		this.bodyDeserializer = bodyDeserializer;
	}

	public void setHeaderDeserializer(IHeaderDeserializer headerDeserializer) {
		this.headerDeserializer = headerDeserializer;
	}

	public void setFaultDeserializer(IFaultDeserializer faultDeserializer) {
		this.faultDeserializer = faultDeserializer;
	}

	public IFaultDeserializer getFaultDeserializer() {
		return faultDeserializer;
	}

	public int getSoapVersion() {
		return soapVersion;
	}

	public void setSoapVersion(int soapVersion) {
		this.soapVersion = soapVersion;
	}

	public XmlPullParser getParser() {
		return parser;
	}

	public EfficientSoapDeserializer() throws XmlPullParserException {
		setBodyDeserializer(new DefaultBodyDeserializerImpl());
		setHeaderDeserializer(new DefaultHeaderDeserializerImpl());
		setFaultDeserializer(new DefaultFaultDeserializerImpl());
		
		// XmlPullParser parser = Xml.newPullParser();
		XmlPullParserFactory factory;
		factory = XmlPullParserFactory.newInstance();
		factory.setNamespaceAware(true);
		this.parser = factory.newPullParser();
		
		
		// set features of the Parser instance 
		//this.parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, false);
		//this.parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
		//this.parser.setFeature(XmlPullParser.FEATURE_REPORT_NAMESPACE_ATTRIBUTES, false);
		//this.parser.setFeature(XmlPullParser.FEATURE_VALIDATION, true); //not necessary - this Implementation validating it self 
		
		
		Log.d(LOGGING_TAG, "Parser instance features:");
		Log.d(LOGGING_TAG, "FEATURE_PROCESS_DOCDECL="+this.parser.getFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL));
		Log.d(LOGGING_TAG, "FEATURE_PROCESS_NAMESPACES="+this.parser.getFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES));
		Log.d(LOGGING_TAG, "FEATURE_REPORT_NAMESPACE_ATTRIBUTES="+this.parser.getFeature(XmlPullParser.FEATURE_REPORT_NAMESPACE_ATTRIBUTES));
		Log.d(LOGGING_TAG, "FEATURE_VALIDATION="+this.parser.getFeature(XmlPullParser.FEATURE_VALIDATION));
		
	}

	public T process(BufferedReader rdr) throws XmlPullParserException,
			IOException, EfficientSoapFaultException {
		Log.d(LOGGING_TAG,"process()");
		this.parser.setInput(rdr);

		this.responseObject = null;
		do {
			parser.nextToken();
			 Log.d(LOGGING_TAG,"process():EVENTTOKEN="+
			 XmlPullParser.TYPES[parser.getEventType()]+parser.getName());		 
		} while (parser.getEventType() != XmlPullParser.START_TAG && parser.getEventType() != XmlPullParser.END_DOCUMENT);
		
		if(parser.getEventType() != XmlPullParser.END_DOCUMENT){
			parseEnvelope();
		}else{
			Log.e(LOGGING_TAG,"END of DOCUMENT reached befor an Envelope tag is readed. check if the message was empty");
		}
		
		return this.responseObject;
	}

	private void skipForParseString() throws XmlPullParserException,
			IOException {
		while (parser.getEventType() == XmlPullParser.COMMENT
				|| parser.getEventType() == XmlPullParser.PROCESSING_INSTRUCTION) {
			parser.nextToken();
		}
	}

	private String parseString() throws XmlPullParserException, IOException {
		skipForParseString();

		int eventType = parser.getEventType();// current event type
		if (eventType == XmlPullParser.TEXT
				|| eventType == XmlPullParser.IGNORABLE_WHITESPACE
				|| eventType == XmlPullParser.CDSECT) {
			String cDataSection = parser.getText();
			parser.nextToken();
			skipForParseString();
			eventType = parser.getEventType();
			while (eventType == XmlPullParser.TEXT
					|| eventType == XmlPullParser.IGNORABLE_WHITESPACE
					|| eventType == XmlPullParser.CDSECT) {
				cDataSection = cDataSection + parser.getText();
				parser.nextToken();
				skipForParseString();
				eventType = parser.getEventType();
			}

			return cDataSection;
		} else
			return "";
	}

	private int parseInt() throws XmlPullParserException, IOException {
		String string = parseString().trim();
		return Integer.parseInt(string);
	}

	private long parseLong() throws XmlPullParserException, IOException {
		String string = parseString().trim();
		return Long.parseLong(string);
	}

	private double parseDouble() throws XmlPullParserException, IOException {
		String string = parseString().trim();
		return Double.parseDouble(string);
	}

	private boolean parseBoolean() throws XmlPullParserException, IOException {
		String string = null;
		string = parseString().trim();
		return Boolean.parseBoolean(string);
	}

	// Primitive Object parsing methods
	public String parseStringElem(String tag) throws XmlPullParserException,
			IOException {
		parser.require(XmlPullParser.START_TAG, null, tag);
		parser.nextToken();
		String s = parseString();
		parser.require(XmlPullParser.END_TAG, null, tag);
		parser.nextToken();
		skipWhitespace();
		return s;
	}

	public int parseIntegerElem(String tag) throws XmlPullParserException,
			IOException {
		parser.require(XmlPullParser.START_TAG, null, tag);
		parser.nextToken();
		int i = parseInt();
		parser.require(XmlPullParser.END_TAG, null, tag);
		parser.nextToken();
		skipWhitespace();
		return i;
	}

	public long parseLongElem(String tag) throws XmlPullParserException,
			IOException {
		parser.require(XmlPullParser.START_TAG, null, tag);
		parser.nextToken();
		long l = parseLong();
		parser.require(XmlPullParser.END_TAG, null, tag);
		parser.nextToken();
		skipWhitespace();

		return l;
	}
	/**
	 * a wrapper method for parseLongElem(String tag)
	*/
	public long parseAmountElem(String tag) throws XmlPullParserException,IOException {
		return parseLongElem(tag);
	}
	
	public double parseDoubleElem(String tag) throws XmlPullParserException,
			IOException {
		parser.require(XmlPullParser.START_TAG, null, tag);
		parser.nextToken();
		double d = parseDouble();
		parser.require(XmlPullParser.END_TAG, null, tag);
		parser.nextToken();
		skipWhitespace();

		return d;
	}

	public boolean parseBooleanElem(String tag) throws XmlPullParserException,
			IOException {
		parser.require(XmlPullParser.START_TAG, null, tag);
		parser.nextToken();
		boolean b = parseBoolean();
		parser.require(XmlPullParser.END_TAG, null, tag);
		parser.nextToken();
		skipWhitespace();

		return b;
	}

	public void skipWhitespace() throws XmlPullParserException, IOException {
		int eventType = parser.getEventType();// current event type
		while ((eventType == XmlPullParser.TEXT && parser.isWhitespace())
				|| (eventType == XmlPullParser.CDSECT && parser.isWhitespace())
				|| eventType == XmlPullParser.IGNORABLE_WHITESPACE
				|| eventType == XmlPullParser.PROCESSING_INSTRUCTION
				|| eventType == XmlPullParser.COMMENT) {

			// get next event Token/Type
			eventType = parser.nextToken();
		}
	}

	/**
	 * Moves the XML parser behind a complete XML element, respecting nested
	 * element structures.
	 */
	public void skipElement() throws XmlPullParserException, IOException {
		// allow only a START TAG to skip
		// parser.require(XmlPullParser.START_TAG, null, null);

		// current event type
		int eventType = parser.getEventType();
		// Log.d(APP_LOGGING_TAG, "start skipElement:EVENTTOKEN="+
		// XmlPullParser.TYPES[parser.getEventType()]+ parser.getName());

		if (eventType == XmlPullParser.TEXT) {
			parseString();
			return;
		}
		if (eventType == XmlPullParser.START_TAG) {
			String name = parser.getName();

			parser.nextToken();
			skipWhitespace();
			while (parser.getEventType() != XmlPullParser.END_TAG) {
				skipElement();
			}
			parser.require(XmlPullParser.END_TAG, null, name);
			skipWhitespace();
		}
		if (eventType != XmlPullParser.END_DOCUMENT) {
			eventType = parser.nextToken();
		}

	}

	protected void parseEnvelope() throws IOException, XmlPullParserException, EfficientSoapFaultException {

		// ResponseObject responseObject = null;
		switch (this.soapVersion) {
		case EfficientSoapDeserializer.VERSION10:
		case EfficientSoapDeserializer.VERSION11:
			soapNamespace = EfficientSoapDeserializer.ENVELOPE;
			break;
		case EfficientSoapDeserializer.VERSION12:
			soapNamespace = EfficientSoapDeserializer.ENVELOPE0503;
			break;
		default:
			soapNamespace = EfficientSoapDeserializer.ENVELOPE;
			break;
		}
		parser.require(XmlPullParser.START_TAG, soapNamespace, "Envelope");
		parser.nextToken();
		skipWhitespace();

		// optional <Header>
		if (parser.getEventType() == XmlPullParser.START_TAG
				&& "Header".equals(parser.getName())) {
			headerDeserializer.parseHeader();
		}
		skipWhitespace();

		this.responseObject = bodyDeserializer.parseBody();
		skipWhitespace();

		parser.require(XmlPullParser.END_TAG, soapNamespace, "Envelope");
		parser.nextToken();
	}

	private class DefaultHeaderDeserializerImpl implements IHeaderDeserializer {
		@Override
		public void parseHeader() throws XmlPullParserException, IOException {
			parser.require(XmlPullParser.START_TAG, soapNamespace, "Header");
			skipElement();
			skipWhitespace();
		}

	}

	private class DefaultBodyDeserializerImpl implements IBodyDeserializer<T> {
		@Override
		public T parseBody() throws IOException, XmlPullParserException {
			parser.require(XmlPullParser.START_TAG, soapNamespace, "Body");
			skipElement();
			skipWhitespace();
			return null;
		}
	}
	
	private class DefaultFaultDeserializerImpl implements IFaultDeserializer {
		
		EfficientSoapFault soapfault = new EfficientSoapFault();
		
		@Override
		public void parseFault() throws XmlPullParserException, IOException, EfficientSoapFaultException {
			
			parser.require(XmlPullParser.START_TAG, soapNamespace, "Fault");
			parser.nextToken();
			
			skipWhitespace();			
			//<Code>
			//	<Value/>
			//	<!-- optional subcode element -->
			//	<Subcode>
			//		<Value/>
			//		<!-- possible recursively nested sub codes -->
			//		<Subcode><Value/></Subcode>
			//	</Subcode>
			//</Code>
			parseCode();
	
			skipWhitespace();
			//Reason: error description in plain text
			//<Reason>
			//	<!-- multiple times one for each language-->
			//	</Text>
			//</Reason>
			parseReason();
			
			skipWhitespace();
			//skip Detail: application specific error massages 
			skipElement();
			
			skipWhitespace();
			parser.require(XmlPullParser.END_TAG, soapNamespace, "Fault");
			parser.nextToken();
			
			throw new EfficientSoapFaultException(soapfault);
		}
		private void parseCode() throws XmlPullParserException, IOException{
			Code code = soapfault.getCode();
			
			parser.require(XmlPullParser.START_TAG, soapNamespace, "Code");
			parser.nextToken();
			
			skipWhitespace();			
			code.setValue(parseStringElem("Value"));
			
			skipWhitespace();			
			if (parser.getEventType() == XmlPullParser.START_TAG && "Subcode".equals(parser.getName())) {
				code.setSubcode(parseSubCode());
			}
			
			soapfault.setCode(code);
			
			skipWhitespace();
			parser.require(XmlPullParser.END_TAG, soapNamespace, "Code");
			parser.nextToken();
			
		}
		
		private SubCode parseSubCode() throws XmlPullParserException, IOException{
			
			SubCode subCode = soapfault.getCode().getSubcode();
			
			parser.require(XmlPullParser.START_TAG, soapNamespace, "Code");
			parser.nextToken();
			
			skipWhitespace();			
			subCode.setValue(parseStringElem("Value"));
			
			skipWhitespace();
			if (parser.getEventType() == XmlPullParser.START_TAG && "Subcode".equals(parser.getName())) {
				
				subCode.setSubcode(parseSubCode());
			}
			
			skipWhitespace();
			parser.require(XmlPullParser.END_TAG, soapNamespace, "Code");
			parser.nextToken();
			
			return subCode;
		}
		
		private void parseReason() throws XmlPullParserException, IOException{
			Reason reason = soapfault.getReason();
			
			parser.require(XmlPullParser.START_TAG, soapNamespace, "Reason");
			parser.nextToken();
			
			skipWhitespace();			
			reason.setText((parseStringList("Text")));

			soapfault.setReason(reason);
			
			skipWhitespace();
			parser.require(XmlPullParser.END_TAG, soapNamespace, "Reason");
			parser.nextToken();			
		}
	
	}

	public List<String> parseStringList(String listElementTag)
			throws XmlPullParserException, IOException {
		Log.d(LOGGING_TAG, "parseList(" + listElementTag + ")");
		List<String> list = new ArrayList<String>();
		while (parser.getEventType() == XmlPullParser.START_TAG
				&& parser.getName().equals(listElementTag)) {
			list.add(parseStringElem(listElementTag));
			skipWhitespace();
		}
		return list;
	}

	public List<Integer> parseIntegerList(String listElementTag)
			throws XmlPullParserException, IOException {
		Log.d(LOGGING_TAG, "parseList(" + listElementTag + ")");
		List<Integer> list = new ArrayList<Integer>();
		while (parser.getEventType() == XmlPullParser.START_TAG
				&& parser.getName().equals(listElementTag)) {
			list.add(parseIntegerElem(listElementTag));
			skipWhitespace();
		}
		return list;
	}

	public List<Long> parseLongList(String listElementTag)
			throws XmlPullParserException, IOException {
		Log.d(LOGGING_TAG, "parseList(" + listElementTag + ")");
		List<Long> list = new ArrayList<Long>();
		while (parser.getEventType() == XmlPullParser.START_TAG
				&& parser.getName().equals(listElementTag)) {
			list.add(parseLongElem(listElementTag));
			skipWhitespace();
		}
		return list;
	}

	public List<Double> parseDoubleList(String listElementTag)
			throws XmlPullParserException, IOException {
		Log.d(LOGGING_TAG, "parseList(" + listElementTag + ")");
		List<Double> list = new ArrayList<Double>();
		while (parser.getEventType() == XmlPullParser.START_TAG
				&& parser.getName().equals(listElementTag)) {
			list.add(parseDoubleElem(listElementTag));
			skipWhitespace();
		}
		return list;
	}

	public List<Boolean> parseBooleanList(String listElementTag)
			throws XmlPullParserException, IOException {
		Log.d(LOGGING_TAG, "parseList(" + listElementTag + ")");
		List<Boolean> list = new ArrayList<Boolean>();
		while (parser.getEventType() == XmlPullParser.START_TAG
				&& parser.getName().equals(listElementTag)) {
			list.add(parseBooleanElem(listElementTag));
			skipWhitespace();
		}
		return list;
	}

}
